#include <iostream>
#include <mutex>

#include <backends/imgui_impl_glfw.h>
#include <backends/imgui_impl_opengl3.h>
#include <include/core/SkBitmap.h>
#include <include/core/SkCanvas.h>
#include <include/core/SkImage.h>
#include <include/core/SkPicture.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <matrix.h>
#include <skia_framework.h>

#include "index_buffer.h"
#include "renderer.h"
#include "shader.h"
#include "texture.h"
#include "vertex_array.h"
#include "vertex_buffer.h"
#include "vertex_buffer_layout.h"

int main()
{
    if (!glfwInit()) {
        std::cerr << "glfwInit failed" << std::endl;
        return -1;
    }

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);

    GLFWwindow* window = glfwCreateWindow(640, 480, "Hello OpenGL", nullptr, nullptr);
    if (window == nullptr) {
        std::cerr << "glfwCreateWindow failed" << std::endl;
        std::cerr << "errno: " << errno << ", means " << strerror(errno) << std::endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);

    if (glewInit() != GLEW_OK) {
        std::cerr << "glewInit failed";
        return -1;
    }

    int32_t width, height;
    glfwGetFramebufferSize(window, &width, &height);
    glViewport(0, 0, width, height);
    {
        float positions[] = {
            -50.0f, -50.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // 0
             50.0f, -50.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 1
             50.0f,  50.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // 2
            -50.0f,  50.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // 3
        };

        uint32_t indices[] = {
            0, 1, 2,
            2, 3, 0,
        };

        GLCALL(glEnable(GL_BLEND));
        GLCALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); // transparent

        VertexArray va;
        VertexBuffer vb(positions, sizeof(positions));

        VertexBufferLayout layout;
        layout.Push<float>(3);
        layout.Push<float>(3);
        layout.Push<float>(2);
        va.AddBuffer(vb, layout);

        IndexBuffer ib(indices, sizeof(indices) / sizeof(*indices));

        glm::mat4 projection = glm::ortho(0.0f, 960.0f, 0.0f, 540.0f, -1.0f, 1.0f);
        glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, 0));

        Shader shader("out/basic.shader");
        shader.Bind();

        Texture texture("out/qq_head.png");
        texture.Bind(0);
        shader.SetUniform1i("u_Texture", 0);
        texture.Bind(1);
        shader.SetUniform1i("u_Texture2", 1);

        va.Unbind();
        vb.Unbind();
        ib.Unbind();
        shader.Unbind();

        Renderer renderer;

        // create fbo
        GLuint framebuffer;
        glGenFramebuffers(1, &framebuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        GLuint textureColorbuffer;
        glGenTextures(1, &textureColorbuffer);
        glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 640, 480, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);
        GLuint rbo;
        glGenRenderbuffers(1, &rbo);
        glBindRenderbuffer(GL_RENDERBUFFER, rbo);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 640, 480);
        glBindRenderbuffer(GL_RENDERBUFFER, 0);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
            std::cerr << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
        }

        GLfloat quadVertices[] = {
            // Positions   // TexCoords
            -1.0f,  1.0f,  0.0f, 1.0f,
            -1.0f, -1.0f,  0.0f, 0.0f,
             1.0f, -1.0f,  1.0f, 0.0f,
            -1.0f,  1.0f,  0.0f, 1.0f,
             1.0f, -1.0f,  1.0f, 0.0f,
             1.0f,  1.0f,  1.0f, 1.0f,
        };
        GLuint quadVAO, quadVBO;
        glGenVertexArrays(1, &quadVAO);
        glGenBuffers(1, &quadVBO);
        glBindVertexArray(quadVAO);
        glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
        glBindVertexArray(0);

        SkiaFramework sf;
        sf.SetDrawFunc([&](SkCanvas &canvas) {
            glfwMakeContextCurrent(window);
            GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, framebuffer));
            renderer.Clear();

            static std::once_flag once;
            std::call_once(once, [&]() {
                texture.Bind(0);
                texture.Bind(1);
            });

            {
                auto model = Matrix<GLfloat>::TranslateMatrix(200, 200);
                model = model * Matrix<GLfloat>::RotateMatrixZ(glfwGetTime());

                shader.Bind();
                shader.SetUniformMat4f("u_ViewProjection", projection * view);
                shader.SetUniformMat4f("u_Model", model);

                renderer.Draw(va, ib, shader);
            }

            {
                auto model = Matrix<GLfloat>::TranslateMatrix(400, 200);

                shader.Bind();
                shader.SetUniformMat4f("u_ViewProjection", projection * view);
                shader.SetUniformMat4f("u_Model", model);

                renderer.Draw(va, ib, shader);
            }

            int image_bytes = width * height * 4;
            unsigned char *image = new unsigned char[image_bytes];
            GLCALL(glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, image));
            SkBitmap bitmap;
            bitmap.setInfo(SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, kOpaque_SkAlphaType));
            bitmap.setPixels(image);
            auto img = SkImage::MakeFromBitmap(bitmap);
            canvas.clear(SK_ColorWHITE);
            canvas.drawImage(img, 0, 0);
            delete []image;
        });
        sf.Run();
    }

    glfwDestroyWindow(window);
    glfwTerminate();
    return 0;
}
